#include "socs/comm/types.h"
#include "socs/comm/constants.h"
#include "socs/comm/socs.h"
#include "socs/mod/addr_opt.h"
#include "socs/mod/sap.h"
#include "socs/mod/neigh.h"
#include "socs/mod/hello.h"
#include "socs/mod/localnode.h"
#include "socs/mod/lladdr_resolve.h"
#include "socs/comm/config.h"

#include <linux/string.h>
#include <linux/rbtree.h>

struct sap_table *saptb4;
struct sap_table *saptb6;
static DEFINE_RWLOCK(saptb_lock4);
static DEFINE_RWLOCK(saptb_lock6);
static rwlock_t *saptb_lock[FAMILY_NUM];

#define SAPTB_READ_LOCK(family) read_lock_bh(saptb_lock[family])
#define SAPTB_READ_UNLOCK(family) read_unlock_bh(saptb_lock[family])
#define SAPTB_WRITE_LOCK(family) write_lock_bh(saptb_lock[family])
#define SAPTB_WRITE_UNLOCK(family) write_unlock_bh(saptb_lock[family])

void saptb_read_lock(int family)
{
	SAPTB_READ_LOCK(family);
}

void saptb_read_unlock(int family)
{
	SAPTB_READ_UNLOCK(family);
}


static int __sap_compare_fn4(const struct sap *sap, const struct in_addr *addr4)
{
	return addr_cmp(IPV4, (void *)&sap->saIP.ipv4, (void *)addr4);
}

static int __sap_compare_fn6(const struct sap *sap,
							 const struct in6_addr *addr6)
{
	return addr_cmp_prefix(IPV6, (void *)&sap->saIP.ipv6, (void *)addr6,
						   sap->prefix);
}

/******************************************************************************/

bool is_sap6(struct in6_addr *address, struct in6_addr *saIP,
			 struct in6_addr *dIP)
{
	/*	saIP should map the sap and locallink is true */
	bool result = false;
	struct sap_table *saptb = saptb_get(IPV6);
	struct rb_node **node, *parent;
	struct sap *sap;
	//struct in6_addr tmp;
	SAPTB_READ_LOCK(IPV6);
	rbtree_find_node(address, &saptb->sap_tree, __sap_compare_fn6, struct sap,
					 tree_hook, parent, node);

	if (*node) {
		sap = rb_entry(*node, struct sap, tree_hook);

		if (sap->status == SERVING && sap->prefix == 128) {
			result = true;
			*saIP = sap->saIP.ipv6;
			*dIP = sap->dIP.ipv6;
		}
	}

	/* saptb_for_each_sap(i, saptb) {                                              */
	/*     result = ((saptb->saps[i].is_locallink)                                 */
	/*               && (saptb->saps[i].status == SERVING)                         */
	/*               && (saptb->saps[i].prefix == 128)                             */
	/*               && !addr_cmp_prefix(IPV6, &saptb->saps[i].saIP.ipv6, address, */
	/*                                   saptb->saps[i].prefix)                    */
	/*              );                                                             */
	/*     if (result) {                                                           */
	/*         *saIP = saptb->saps[i].saIP.ipv6;                                   */
	/*         *dIP = saptb->saps[i].dIP.ipv6;                                     */
	/*         goto end;                                                           */
	/*     }                                                                       */
	/* }                                                                           */
/* end: */
	SAPTB_READ_UNLOCK(IPV6);
	return result;
}

bool is_sap(int family, void *address, int *is_nat, struct in_addr *addr_64)
{
	/*	saIP should map the sap and locallink is true */
	bool result = false;
	struct sap_table *saptb = saptb_get(family);
	struct rb_node **node, *parent;
	struct sap *sap;
	struct ipv6_prefix prefix6;

	SAPTB_READ_LOCK(family);

	if (family == IPV4) {
		rbtree_find_node(address, &saptb->sap_tree, __sap_compare_fn4, struct sap,
						 tree_hook, parent, node);
	} else {
		rbtree_find_node(address, &saptb->sap_tree, __sap_compare_fn6, struct sap,
						 tree_hook, parent, node);
	}

	if (*node) {
		sap = rb_entry(*node, struct sap, tree_hook);

		if (sap->is_locallink && (sap->status == SERVING)) {
			result = true;
		}
		if (is_nat && addr_64 && family == IPV6 && sap->prefix <= 96) {
			memcpy(&prefix6.address, &sap->saIP.ipv6, sizeof(struct in6_addr));
			prefix6.len = sap->prefix;
			if(!addr_6to4(address, &prefix6, addr_64))
				*is_nat = 1;
		}
	}

	/* saptb_for_each_sap(i, saptb) {                                          */
	/*     if (family == IPV4)	{                                               */
	/*         result = ((saptb->saps[i].is_locallink)                         */
	/*                   && (saptb->saps[i].status == SERVING)                 */
	/*                   && !addr_cmp(family, &saptb->saps[i].saIP, address)); */
	/*         if (result) {                                                   */
	/*             break;                                                      */
	/*         }                                                               */
	/*     } else {                                                            */
	/*         if ((saptb->saps[i].status == SERVING) &&                       */
	/*             !addr_cmp_prefix(family, &saptb->saps[i].saIP, address,     */
	/*                              saptb->saps[i].prefix)) {                  */
	/*             if (tmp_prefix < saptb->saps[i].prefix) {                   */
	/*                 tmp_prefix = saptb->saps[i].prefix;                     */
	/*             }                                                           */
	/*             ret = (saptb->saps[i].is_locallink);                        */
	/*         } else {                                                        */
	/*             ret = false;                                                */
	/*         }                                                               */
	/*         // return when matching an ipv6 host address exactly            */
	/*         // continue when matching an ipv6 prefix                        */
	/*         if (tmp_prefix == 128) {                                        */
	/*             break;                                                      */
	/*         }                                                               */
	/*     }                                                                   */
	/* }                                                                       */
	/* if (family == IPV6) {                                                   */
	/*     if (tmp_prefix == 128) {                                            */
	/*         result = ret;                                                   */
	/*     } else if (tmp_prefix > 0) {                                        */
	/*         result = true;                                                  */
	/*     }                                                                   */
	/* }                                                                       */
	SAPTB_READ_UNLOCK(family);
	return result;
}

bool is_sap_arp(void *address, void *dIP)
{
	/*	saIP should map the sap and locallink is true */
	bool result = false;
	struct sap_table *saptb = saptb_get(IPV4);
	struct rb_node **node, *parent;
	struct sap *sap;
	SAPTB_READ_LOCK(IPV4);
	rbtree_find_node(address, &saptb->sap_tree, __sap_compare_fn4, struct sap,
					 tree_hook, parent, node);

	if (*node) {
		sap = rb_entry(*node, struct sap, tree_hook);

		if (sap->is_locallink && (sap->status == SERVING)) {
			*(struct in_addr *)dIP = sap->dIP.ipv4;
			result = true;
		}
	}

	/* saptb_for_each_sap(i, saptb) {                                                         */
	/*     result = (!addr_cmp(IPV4, &saptb->saps[i].saIP, address)                           */
	/*               && (saptb->saps[i].is_locallink) && (saptb->saps[i].status == SERVING)); */
	/*     if (result) {                                                                      */
	/*         *(struct in_addr *)dIP = saptb->saps[i].dIP.ipv4;                              */
	/*         goto end;                                                                      */
	/*     }                                                                                  */
	/* }                                                                                      */
/* end: */
	SAPTB_READ_UNLOCK(IPV4);
	return result;
}


struct sap_table *saptb_get(int family)
{
	if (family == IPV4) {
		return saptb4;
	} else {
		return saptb6;
	}
}

/* static void __sap_print_rbtree(struct sap_table *saptb)                                  */
/* {                                                                                        */
/*     struct rb_node *node;                                                                */
/*     printk("MAYC %s begin\n", __func__);                                                 */

/*     for (node = rb_first(&saptb->sap_tree); node; node = rb_next(node)) {                */
/*         if (saptb->family == IPV4) {                                                     */
/*             printk("MAYC %pI4c\n", &(rb_entry(node, struct sap, tree_hook)->saIP.ipv4)); */
/*         } else {                                                                         */
/*             printk("MAYC %pI6c\n", &(rb_entry(node, struct sap, tree_hook)->saIP.ipv6)); */
/*         }                                                                                */
/*     }                                                                                    */

/*     printk("MAYC %s end\n", __func__);                                                   */
/* }                                                                                        */

static int __sap_add(void *saIP, struct sap_table *saptb, int prefix)
{
	int ret = -1;
	struct sap *entry;

	if (!saIP) {
		log_err(ERR_NULL_PTR, "%s NULL is not a valid saIP.", __func__);
		return -1;
	}

	entry = kmalloc(sizeof(*entry), GFP_ATOMIC);

	if (!entry) {
		log_err(ERR_NULL_PTR, "%s kmalloc rbtree entry failed.", __func__);
		return -1;
	}
	memset(entry, 0, sizeof(*entry));
	entry->prefix = prefix;
	entry->status = NODIP;

	if (saptb->family == IPV4) {
		memcpy(&entry->saIP.ipv4, saIP, sizeof(struct in_addr));
		ret = rbtree_add(entry, &entry->saIP.ipv4,
						 &saptb->sap_tree, __sap_compare_fn4,
						 struct sap,
						 tree_hook);
	} else {
		memcpy(&entry->saIP.ipv6, saIP, sizeof(struct in6_addr));
		ret = rbtree_add(entry, &entry->saIP.ipv6,
						 &saptb->sap_tree, __sap_compare_fn6,
						 struct sap,
						 tree_hook);
	}

	if (!ret) {
		saptb->sap_num++;
	}

	return ret;
}

int saptb_vote_for_expire(struct sap_table *saptb, void *expire)
{
	//unsigned int tmDead=9;
	//int err=0;
	if (saptb->family == IPV4) {
		log_debug("saptb_vote_for_expire,expire %pI4", (struct in_addr *)expire);
	} else {
		log_debug("saptb_vote_for_expire,expire %pI6c", (struct in6_addr *)expire);
	}

	SAPTB_WRITE_LOCK(saptb->family);
	socs_neigh_vote(saptb);
	SAPTB_WRITE_UNLOCK(saptb->family);
	/* saptb_show(saptb, 0); */
	return 0;
}

struct sap *saptb_iterator(struct sap_table *saptb, struct sap *sap)
{
	struct rb_node *node;

	if (saptb) {
		node = rb_first(&saptb->sap_tree);
	} else if (sap) {
		node = rb_next(&sap->tree_hook);
	}

	if (node) {
		return rb_entry(node, struct sap, tree_hook);
	}

	return NULL;
}

int saptb_vote(struct sap_table *saptb)
{
	log_debug("saptb_vote for %d", saptb->family);
	SAPTB_WRITE_LOCK(saptb->family);
	socs_neigh_vote(saptb);
	SAPTB_WRITE_UNLOCK(saptb->family);
	return 0;
}


/* [>*******************************************************************************************<] */
/* [>*******************************************************************************************<] */
/* void saptb_show(struct sap_table *saptb, int show_locallink)                                    */
/* {                                                                                               */
/*     int i, end;                                                                                 */
/*     log_debug("sap_table: family=%d,locallinkcnt=%u,sap_num=%u", saptb->family,                 */
/*               saptb->locallinkcnt, saptb->sap_num);                                             */
/*     log_debug("saIP		 dIP		locallink     status");                                              */
/*     SAPTB_READ_LOCK(saptb->family);                                                             */

/*     if (show_locallink) {                                                                       */
/*         end = saptb->locallinkcnt;                                                              */
/*     } else {                                                                                    */
/*         end = saptb->sap_num;                                                                   */
/*     }                                                                                           */

/*     for (i = 0; i < end; i++) {                                                                 */
/*         if (saptb->family == IPV4) {                                                            */
/*             log_debug("%pI4	 %pI4	 %d            %d", &saptb->saps[i].saIP.ipv4,                */
/*                       &saptb->saps[i].dIP.ipv4,                                                 */
/*                       saptb->saps[i].is_locallink, saptb->saps[i].status);                      */
/*         } else {                                                                                */
/*             log_debug("%pI6c %pI6c	 %d            %d", &saptb->saps[i].saIP.ipv6,               */
/*                       &saptb->saps[i].dIP.ipv6,                                                 */
/*                       saptb->saps[i].is_locallink, saptb->saps[i].status);                      */
/*         }                                                                                       */
/*     }                                                                                           */

/*     SAPTB_READ_UNLOCK(saptb->family);                                                           */
/* }                                                                                               */
int saptb_init(char **str_saIP4, int saIP4_size, char **str_saIP6,
			   int saIP6_size)
{
//	log_debug("sap_init :saIP4:%s,size=%d,saIP6:%s,size=%d\n",*saIP4,saIP4_size,*saIP6,saIP6_size);
	/*cat use the default saIP dIP to do test*/
	struct in_addr saIP4;
	struct in6_addr saIP6;
	int i;
	char *temp_ipv6 = NULL;
	int prefix;
	saptb_lock[IPV4] = &saptb_lock4;
	saptb_lock[IPV6] = &saptb_lock6;
	saptb4 = (struct sap_table *)kmalloc(sizeof(struct sap_table), GFP_ATOMIC);
	memset(saptb4, 0, sizeof(struct sap_table));
	saptb4->locallinkcnt = 0;
	saptb4->sap_num = 0;
	saptb4->family = IPV4;
	saptb4->sap_tree = RB_ROOT;
	SAPTB_WRITE_LOCK(IPV4);

	for (i = 0; i < saIP4_size; i++) {
		memset(&saIP4, 0, sizeof(struct in_addr));
		str_to_addr4(str_saIP4[i], &saIP4);
		__sap_add(&saIP4, saptb4, 8);
	}

	SAPTB_WRITE_UNLOCK(IPV4);
	saptb6 = (struct sap_table *)kmalloc(sizeof(struct sap_table), GFP_ATOMIC);
	memset(saptb6, 0, sizeof(struct sap_table));
	saptb6->locallinkcnt = 0;
	saptb6->sap_num = 0;
	saptb6->family = IPV6;
	saptb6->sap_tree = RB_ROOT;
	SAPTB_WRITE_LOCK(IPV6);

	for (i = 0; i < saIP6_size; i++) {
		prefix = 128;
		temp_ipv6 = NULL;
		memset(&saIP6, 0, sizeof(struct in6_addr));
		temp_ipv6 = strsep(&str_saIP6[i], "/");

		if (str_saIP6[i]) {
			str_to_addr6(temp_ipv6, &saIP6);
			sscanf(str_saIP6[i], "%d", &prefix);
		} else {
			str_to_addr6(temp_ipv6, &saIP6);
			prefix = 128;
		}

		__sap_add(&saIP6, saptb6, prefix);
	}

	SAPTB_WRITE_UNLOCK(IPV6);
	/* saptb_show(saptb4, 0); */
	/* saptb_show(saptb6, 0); */
	return 0;
}
void saptb_flush(int family) /*just flush the vote info*/
{
	/* int i; */
	struct sap_table *saptb = saptb_get(family);
	struct sap *sap;
	log_debug("saptb_flush %d", family);
	SAPTB_WRITE_LOCK(saptb->family);
	saptb->locallinkcnt = 0;
	/* saptb_for_each_sap(i, saptb) {                                       */
	/*     memset(&saptb->saps[i].dIP, 0,                                   */
	/*            sizeof(saptb->saps[i].dIP)); //modiy by huangjf 2015-7-20 */
	/*     saptb->saps[i].is_locallink = 0;                                 */
	/* }                                                                    */
	sap = saptb_iterator(saptb, NULL);

	while(sap) {
		memset(&sap->dIP, 0, sizeof(sap->dIP));
		sap->is_locallink = 0;
		sap = saptb_iterator(NULL, sap);
	}

	SAPTB_WRITE_UNLOCK(saptb->family);
}

static void sap_destructor(struct rb_node *node)
{
	struct sap *sap;
	sap = rb_entry(node, struct sap, tree_hook);
	kfree(sap);
}

void saptb_destroy(void)
{
	SAPTB_WRITE_LOCK(IPV4);
	rbtree_clear(&saptb4->sap_tree, sap_destructor);
	kfree(saptb4);
	SAPTB_WRITE_UNLOCK(IPV4);
	SAPTB_WRITE_LOCK(IPV6);
	rbtree_clear(&saptb6->sap_tree, sap_destructor);
	kfree(saptb6);
	SAPTB_WRITE_UNLOCK(IPV6);
}
